package cli

import (
	cecdsa "crypto/ecdsa"
	"encoding/json"
	"fmt"
	"math/rand"
	"osiris/config"
	"osiris/core/txpool"
	"osiris/dao"
	"osiris/dto"
	"osiris/logger"
	"osiris/model"
	"osiris/ocrypto/ecdsa"
	"osiris/p2p"
	"sync"
	"time"
)

// var 虚拟客户端管理者
var (

	// autoTx 是否启用autoTX
	autoTx bool

	// autoTxMutex autoTx变量的读写锁
	autoTxMutex *sync.RWMutex

	// localClients 已激活的本地虚拟客户端
	localClients []*VirtualClient

	// confirmingAccounts 待上链确认的账户
	confirmingAccounts map[string]*cecdsa.PrivateKey

	localMutex *sync.RWMutex

	confirmingAccountsMutex *sync.RWMutex

	// confirmCh 通知交易确认的通道
	confirmCh chan dto.TxData
)

func init() {
	autoTx = false
	autoTxMutex = new(sync.RWMutex)
	localMutex = new(sync.RWMutex)
	confirmingAccountsMutex = new(sync.RWMutex)
	confirmingAccounts = make(map[string]*cecdsa.PrivateKey, 0)
	confirmCh = make(chan dto.TxData, 32)
}

// InitClientManager 初始化虚拟客户端
func InitClientManager() {
	localMutex.Lock()
	defer localMutex.Unlock()

	//把local中的所有账户加入虚拟客户端
	localModels, err := dao.GetAllLocal()
	if err != nil {
		logger.Error(map[string]interface{}{"[cli] [Init Client Manager] dao.GetAllLocal()": err})
		return
	}

	for _, localModel := range localModels {
		privKey, err := ecdsa.StrToECDSAPrivKey(localModel.PrivKey)
		if err != nil {
			logger.Error(map[string]interface{}{"[cli] [Init Client Manager] ecdsa.StrToECDSAPrivKey()": err})
			continue
		}

		localClients = append(localClients, &VirtualClient{
			Addr:    ecdsa.StrToAccountBytes(localModel.Addr),
			PrivKey: privKey,
		})
	}

	//订阅本地交易池的交易确认
	txpool.AddLocalSubscribe(confirmCh)
	//监听确认信号
	go func() {
		for txData := range confirmCh {
			onTxConfirmed(txData)
		}
		logger.Warn(map[string]interface{}{"[cli] [Init Client Manager] wait for ch signals": "ch is closed"})
	}()
}

// addLocalClient 向虚拟客户端添加新的账户
//
//	@param addr
//	@param privKey
func addLocalClient(addr []byte, privKey *cecdsa.PrivateKey) {
	localMutex.Lock()
	defer localMutex.Unlock()
	localClients = append(localClients, &VirtualClient{
		Addr:    addr,
		PrivKey: privKey,
	})
}

// AddConfirmingClient 添加待上链确认的本地账户地址
//  @param addrStr 
//  @param privKey 
func AddConfirmingClient(addrStr string, privKey *cecdsa.PrivateKey) {
	confirmingAccountsMutex.Lock()
	defer confirmingAccountsMutex.Unlock()
	confirmingAccounts[addrStr] = privKey
}

// onTxConfirmed 当交易被确认：
//  @param txData 
func onTxConfirmed(txData dto.TxData) {
	switch txData.TxType {
	case dto.TX_REGISTER:
		onRegisterTxConfirmed(txData.FromAddr)

	default:
	}
}

// onRegisterTxConfirmed 当账户注册交易被确认
//  @param addrStr 
func onRegisterTxConfirmed(addrStr string) {
	confirmingAccountsMutex.Lock()
	defer confirmingAccountsMutex.Unlock()

	privKey, exist := confirmingAccounts[addrStr]
	if !exist {
		return
	}

	addrBytes := ecdsa.StrToAccountBytes(addrStr)

	//添加虚拟客户端
	addLocalClient(addrBytes, privKey)

	//从待上链确认账户缓存中删除
	delete(confirmingAccounts, addrStr)

	//向本地交易池中添加本地账户
	txpool.AddLocalAddr(addrBytes)

	//将本地账户存入local表
	privKeyStr := ecdsa.ECDSAPrivKeyToStr(privKey)
	dao.AddLocalByModel(model.LocalModel{
		Addr:    addrStr,
		PrivKey: privKeyStr,
	})
}

// StartAutoTx 开启自动随机交易
func StartAutoTx() {
	autoTxMutex.Lock()
	autoTx = true
	autoTxMutex.Unlock()

	go func() {

		for {
			if !isAutoTx() {
				return
			}

			//每隔8s生成交易
			time.Sleep(7 * time.Second)

			//没有本地账户时优先执行账户创建交易
			if len(localClients) == 0 {
				txData := doRegisterTx()
				commitTx(txData)

				continue
			}

			r := rand.Float32()
			if r < 0.1 {
				//0.1的概率进行账户注册
				txData := doRegisterTx()
				commitTx(txData)
			} else {
				//随机选择本地账户地址生成随机交易
				client := chooseRandomClient()
				txData := client.DoRandomTx()
				commitTx(txData)
			}
		}

	}()
}

// StopAutoTx 停止自动随机交易
func StopAutoTx() {
	autoTxMutex.Lock()
	autoTx = false
	autoTxMutex.Unlock()
}

// ResetClient 重置虚拟客户端
func ResetClient() {
	localClients = make([]*VirtualClient, 0)
}

func isAutoTx() bool {
	autoTxMutex.RLock()
	result := autoTx
	autoTxMutex.RUnlock()
	return result
}

func chooseRandomClient() *VirtualClient {
	localMutex.RLock()
	defer localMutex.RUnlock()
	randNum := rand.Intn(len(localClients))
	return localClients[randNum]
}

func doRegisterTx() *dto.TxData {
	//生成账户私钥、地址
	privKey, addrStr, nonce, err := ecdsa.GenerateAccount()
	if err != nil {
		logger.Error(map[string]interface{}{"[cli] [do Register Tx] ecdsa.GenerateAccount()": err})
		return nil
	}
	registerTxData := dto.TxData{
		TxType:   dto.TX_REGISTER,
		FromAddr: addrStr,
		ToAddr:   addrStr,
		Nonce:    nonce,
		MaxNonce: config.MaxAccountNonce.Int64(),
	}

	//打时间戳、用账户的私钥对交易签名
	registerTxData.HashAndSign(privKey, true)

	//向待上链确认账户缓存中添加账户
	AddConfirmingClient(addrStr, privKey)

	return &registerTxData
}

// commitTx 向交易池中添加交易，将交易内容打包成TxDto后广播交易
//
//	@param txData
func commitTx(txData *dto.TxData) {
	if txData == nil {
		return
	}

	go func() {
		txDto := p2p.GenerateTxDto(*txData)
		jsonStr, err := json.Marshal(txDto)
		if err != nil {
			logger.Error(map[string]interface{}{"[cli] [commit Tx] marshal txDto": err})
		} else {
			p2p.OpenStream(p2p.TxProtocol{}, "", string(jsonStr), nil)
		}
	}()

	//向本地交易池中添加交易
	txpool.AddLocalTx(txData)

	logger.Debug(map[string]interface{}{"[cli] [commit Tx]": fmt.Sprintf("[txType: %d,  timestamp: %d,  txHash: %s,  fromAddr: %s,  toAddr: %s,  amount: %d]", txData.TxType, txData.Timestamp, txData.TxHash, txData.FromAddr, txData.ToAddr, txData.Amount)})
}
